from concepts import *


class GoodType(type):
    @property
    @memoized
    def is_abstract(self):
        return len(self.subtypes) > 0
    
    @property
    @memoized
    def supertypes(self):
        return set(self.mro()) - set([self])
    
    @property
    @memoized
    def all_supertypes(self):
        return set(self.mro())
    
    @property
    @memoized
    def subtypes(self):
        return self.__subclasses__()
    
    @property
    @memoized
    def all_subtypes(self):
        all = set()
        
        for type in self.subtypes:
            all |= type.all_subtypes_and_self
        
        return all
    
    @property
    @memoized
    def all_subtypes_and_self(self):
        return self.all_subtypes | set([self])
    
    @property
    @memoized
    def all_concrete_subtypes(self):
        return set(type for type in self.all_subtypes_and_self if not type.is_abstract)
    
    @memoized
    def is_abstraction_of(self, type):
        if not self.is_abstract:
            return False
        
        return type in self.all_subtypes
    
    @memoized
    def is_abstraction_of_or_self(self, type):
        return self == type or self.is_abstraction_of(type)
    
    @memoized
    def can_add_to(self, type):
        return not self.is_abstract and issubclass(self, type)
    
    @memoized
    def can_take_from(self, type):
        return not type.is_abstract and (self == type or (self.is_abstract and self.is_abstraction_of(type)))


class Good(Object):
    __metaclass__ = GoodType
    
    color      =_ (None)
    dark_color =_ (None)
    
    quantity = Attribute(convert = int)
    
    @property
    def singular(self):
        return self.__class__.__name__
    
    @property
    def plural(self):
        return self.singular + 's'
    
    @property
    def name(self):
        if self.quantity == 1:
            return self.singular
        else:
            return self.plural
    
    @property
    def type(self):
        return self.__class__
    
    def min(self, n, handler = None):
        if n is None:
            return self
        elif self.quantity >= n:
            return self
        else:
            if handler is not None:
                handler(self, n - self.quantity)
            
            return self.type(n)
    
    def max(self, n, handler = None):
        if n is None:
            return self
        elif self.quantity <= n:
            return self
        else:
            if handler is not None:
                handler(self, self.quantity - n)
            
            return self.type(n)
    
    def __add__(self, other):
        if isinstance(other, int):
            return self.type(self.quantity + other)
        elif other.type.can_add_to(self.type):
            return self.type(self.quantity + other.quantity)
        else:
            return NotImplemented
    
    def __sub__(self, other):
        if isinstance(other, int):
            return self.type(self.quantity - other)
        elif other.type.can_take_from(self.type):
            return self.type(self.quantity - other.quantity)
        else:
            return NotImplemented
    
    def __mul__(self, other):
        if isinstance(other, int):
            return self.type(self.quantity * other)
        elif isinstance(other, float):
            return self.type(int(self.quantity * other))
        else:
            return NotImplemented
    
    def __truediv__(self, other):
        return divmod(self, other)
    
    def __floordiv__(self):
        if isinstance(other, int):
            return self.type(self.quantity // other)
        else:
            return NotImplemented
    
    def __divmod__(self, other):
        if isinstance(other, int):
            return (self // other, self % other)
        else:
            return NotImplemented
    
    def __mod__(self, other):
        if isinstance(other, int):
            return self.type(self.quantity % other)
        else:
            return NotImplemented
    
    __radd__ = __add__
    
    def __rsub__(self, other):
        return -self + other
    
    def __rmul__(self, other):
        return self * other
    
    def __neg__(self):
        return self.type(-self.quantity)
    
    def __pos__(self):
        return self
    
    def __abs__(self):
        return self.type(abs(self.quantity))
    
    def __bool__(self):
        return self.quantity != 0
    
    def __int__(self):
        return self.quantity
    
    def __float__(self):
        return float(self.quantity)
    
    def __eq__(self, other):
        if isinstance(other, int):
            return self.quantity == other
        elif self.is_like(other):
            return self.quantity == other.quantity
        else:
            return False
    
    def __ne__(self, other):
        return not self == other
    
    def __lt__(self, other):
        if isinstance(other, int) or isinstance(other, float):
            return self.quantity < other
        elif self.is_like(other):
            return self.quantity < other.quantity
        else:
            return NotImplemented
    
    def __gt__(self, other):
        if isinstance(other, int) or isinstance(other, float):
            return self.quantity > other
        elif self.is_like(other):
            return self.quantity > other.quantity
        else:
            return NotImplemented
    
    def __le__(self, other):
        return self < other or self == other
    
    def __ge__(self, other):
        return self > other or self == other
    
    def __hash__(self):
        return hash(self.type) + self.quantity
    
    def __repr__(self):
        return '%s(%d)' % (self.type.__name__, self.quantity)
    
    def __str__(self):
        return '%d %s' % (self.quantity, self.name)
